from django.template import Context, loader, RequestContext
from django.template.loader import get_template
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib.auth import logout, login, authenticate
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.core.serializers import serialize
from django.core.mail import send_mail
from django.utils import simplejson
from django import newforms as forms

# Create your views here.
from movie.vote.models import Movie
from movie.vote.models import Screening

import datetime
import random
import string

# How many movies to show per page
DEFAULT_COUNT = 25

class NewUserForm(forms.Form):
  username = forms.CharField(max_length=30)
  email = forms.EmailField()
  password1 = forms.CharField(max_length=30, widget=forms.PasswordInput, label='Password')
  password2 = forms.CharField(max_length=30, widget=forms.PasswordInput, label='Password (again)')

  def clean(self):
    """Make sure password fields match and user doesn't exist."""
    if 'password1' not in self.cleaned_data or 'password2' not in self.cleaned_data:
      raise forms.ValidationError('Password not specified')
    if not self.cleaned_data['password1']:
      raise forms.ValidationError('Password cannot be blank')
    if self.cleaned_data['password1'] != self.cleaned_data['password2']:
      raise forms.ValidationError('Passwords do not match')

    # Check for username existence
    if 'username' not in self.cleaned_data or not self.cleaned_data['username']:
      raise forms.ValidationError('No username specified')
    if User.objects.filter(username=self.cleaned_data['username']):
      raise forms.ValidationError('A user already exists with that name')

    # Check for email existence
    if User.objects.filter(email=self.cleaned_data['email']):
      raise forms.ValidationError('A user already exists with that email address')
    return self.cleaned_data

class ChangePasswordForm(forms.Form):
  password1 = forms.CharField(max_length=30, widget=forms.PasswordInput, label='New Password')
  password2 = forms.CharField(max_length=30, widget=forms.PasswordInput, label='New Password (again)')

  def clean(self):
    """Make sure password fields match."""
    if 'password1' not in self.cleaned_data or 'password2' not in self.cleaned_data:
      raise forms.ValidationError('Password not specified')
    if not self.cleaned_data['password1']:
      raise forms.ValidationError('Password cannot be blank')
    if self.cleaned_data['password1'] != self.cleaned_data['password2']:
      raise forms.ValidationError('Passwords do not match')
    return self.cleaned_data

class ForgotPasswordForm(forms.Form):
  email = forms.EmailField()

  def clean(self):
    """Make sure email exists in db."""
    if 'email' not in self.cleaned_data:
      raise forms.ValidationError('Email address not entered')
    email = self.cleaned_data['email']
    if not User.objects.filter(email=email):
      raise forms.ValidationError('No account with that address')
    return self.cleaned_data

def set_preference(request, name, value):
  request.session[name] = value

def get_preference(request, name, default=None, cast=None):
  pref = default
  if name in request.GET:
    pref = request.GET[name]
  elif name in request.session:
    pref = request.session[name]
  if cast:
    try:
      pref = cast(pref)
    except (TypeError, ValueError):
      pref = default
  return pref

def random_string(length):
  alphanum = string.letters + string.digits
  return ''.join([x for i in range(length) for x in random.choice(alphanum)])

def get_sort_description(value):
  if value.startswith('-'):
    order = 'descending'
    value = value[1:]
  else:
    order = 'ascending'
  if value == 'date_added':
    value = 'addition date'
  elif value == 'review_average_score':
    value = 'review average score';
  return '%s (%s)' % (value, order)
  
def get_movies_for_ui(userid, sort_criteria=None, movie_name=None, movie_like=False):
  movies = Movie.objects.extra(select={
    # Plus votes
    'voter_count': 'select count(*) from vote_movie_voters where movie_id = vote_movie.name',
    # Minus votes
    'downvoter_count': 'select count(*) from vote_movie_downvoters where movie_id = vote_movie.name',
    # Has this user voted?
    'voted': 'select count(*) from vote_movie_voters where movie_id = vote_movie.name '
             'and user_id=%s',
    # Has this user downvoted?
    'downvoted': 'select count(*) from vote_movie_downvoters where movie_id = vote_movie.name '
             'and user_id=%s',
    # Number of screenings
    'screenings': 'select count(*) from vote_screening where movie_id = vote_movie.name',
  }, params=[userid, userid])
  if sort_criteria:
    movies = movies.order_by(*sort_criteria)
  if movie_name:
    if movie_like:
      movies = movies.filter(pk__icontains=movie_name)
    else:
      movies = movies.filter(pk=movie_name)

  for movie in movies:
    screening_stat = ''
    if movie.screenings:
      screening_stat = '/S=%s' % (movie.screenings,)
    movie.tally_detail = '(+%s/-%s%s)' % (movie.voter_count,
                                          movie.downvoter_count,
                                          screening_stat)
  return movies

def movie_to_dict(movie):
  movie_dict = {}
  for field in ('name', 'screenings', 'tally_detail', 'imdb_url',
                'rotten_url', 'metacritic_url', 'voter_count',
                'downvoter_count', 'voted', 'tally', 'downvoted',
                'review_average_score'):
    movie_dict[field] = str(getattr(movie, field))
  return movie_dict

@login_required
def index(request, output=None):
  sort_map = {
    'tally': {
      'desc': 'Sort by tally',
      'url': 'sort1=-tally&sort2=name',
    },
    'date_added': {
      'desc': 'Sort by newest movies',
      'url': 'sort1=-date_added&sort2=name',
    },
    'review_average_score': {
      'desc': 'Sort by review score',
      'url': 'sort1=-review_average_score&sort2=name',
    },
  }
  allowed_sort_keys = ('tally', 'name', 'date_added', 'review_average_score')

  sort1 = get_preference(request, 'sort1', '-tally')
  sort2 = get_preference(request, 'sort2', 'name')
  # Some error checking as a user can lock himself out if he munges the fields
  if sort1.lstrip('-') not in allowed_sort_keys:
    sort1 = '-tally'
  if sort2.lstrip('-') not in allowed_sort_keys:
    sort2 = 'name'

  if sort1 == '-tally' and sort2 == 'name':
    # Default sort provides link to sort by latest movies and score
    sort_links = (sort_map['date_added'], sort_map['review_average_score'])
  elif sort1 == '-date_added' and sort2 == 'name':
    # Latest movies sort provides link to sort by tally and score
    sort_links = (sort_map['tally'], sort_map['review_average_score'])
  else:
    sort_links = (sort_map['tally'], sort_map['date_added'])
  set_preference(request, 'sort1', sort1)
  set_preference(request, 'sort2', sort2)
  # Create sorting description
  sort_description = ' and '.join([get_sort_description(sort1), get_sort_description(sort2)])

  sort_criteria = []
  if sort1:
    sort_criteria.append(sort1)
    # Use a secondary sort by non-weighted tallies when sorting by tally
    if sort1 == '-tally':
      sort_criteria.append('-tally_noweight')
  if sort2:
    sort_criteria.append(sort2)
  
  start = get_preference(request, 'start', 0, int)
  count = get_preference(request, 'count', DEFAULT_COUNT, int)
  old_count = get_preference(request, 'old_count', DEFAULT_COUNT, int)
  set_preference(request, 'count', count)

  # Movie search
  search_name = get_preference(request, 'namelike', '')
  movies = get_movies_for_ui(request.user.id, sort_criteria, movie_name=search_name,
                             movie_like=True)
  movie_count = len(movies)
  if count:
    movies = movies[start:(count + start)]

  if output:
    return HttpResponse(simplejson.dumps([movie_to_dict(x) for x in movies]),
                        mimetype='application/%s' % (str(output),))

  # What movie number to start at for the last page
  last_page_start = count * (movie_count//max(1, count))
  return render_to_response('index.html',
                            {'movies': movies,
                             'sort_links': sort_links,
                             'sort_description': sort_description,
                             'start': start,
                             'count': count,
                             'old_count': old_count,
                             'first': 0,
                             'last': last_page_start,
                             'next': min(last_page_start, start + count),
                             'prev': max(0, start - count),
                             'search_value': search_name,
                            },
                            context_instance=RequestContext(request))

@login_required
def voters(request, movie_name):
  movie = get_object_or_404(Movie, pk=movie_name)
  voters = movie.voters.order_by('username')
  downvoters = movie.downvoters.order_by('username')
  for voter in voters:
    voter.weight = movie.get_weight(voter)
  for voter in downvoters:
    voter.weight = movie.get_weight(voter)
  return render_to_response('voters.html', {'movie': movie, 'voters': voters,
                                            'downvoters': downvoters})

@login_required
def user_stats(request, user=None):
  if user:
    user = get_object_or_404(User, username=user)
  else:
    user = request.user

  total_screenings = Screening.objects.count()
  inception_date = user.date_joined.date()
  attended = user.attended_screenings.count()
  if attended:
    oldest_screen_date = user.attended_screenings.order_by('date')[0].date
    if oldest_screen_date < inception_date:
      inception_date = oldest_screen_date

  screenings_inception = Screening.objects.filter(date__gte=inception_date)
  missed = screenings_inception.count() - attended
  attended_recent = 0
  missed_recent = 0
  for screening in screenings_inception.order_by('-date')[:Movie.RECENT_SCREENINGS]:
    if screening.attendees.filter(username=user.username).count():
      attended_recent += 1
    else:
      missed_recent += 1

  all_time_score = max(-0.5, min(0.5, 0.05 * (attended - missed)))
  recent_score = 0.1 * (attended_recent - missed_recent)
  lock = 0
  if attended >= 1:
    lock = 0.5
  weight = max(0, min(1, lock + all_time_score + recent_score))

  stats = {}
  stats['total_screenings'] = total_screenings
  stats['screenings_inception'] = screenings_inception.count()
  stats['attended'] = attended
  stats['missed'] = missed
  stats['attended_recent'] = attended_recent
  stats['missed_recent'] = missed_recent
  stats['all_time_score'] = all_time_score
  stats['recent_score'] = recent_score
  stats['lock'] = lock
  stats['weight'] = weight
  stats['username'] = user.username

  return render_to_response('user_stats.html', stats)

@login_required
def update_tallies(request):
  Movie.update_all_tallies()
  return HttpResponseRedirect(request.META.get('HTTP_REFERER',
                              reverse('movie.vote.views.index')))

@login_required
def minus_vote(request, movie_name, output=None):
  movie = get_object_or_404(Movie, pk=movie_name)
  voters = movie.voters.filter(username=request.user.username)
  if voters:
    movie.voters.remove(request.user)
  else:
    movie.downvoters.add(request.user)
  movie.update_tally()
  if output:
    movie_dict = movie_to_dict(get_movies_for_ui(request.user.id, movie_name=movie_name)[0])
    return HttpResponse(simplejson.dumps(movie_dict),
                        mimetype='application/%s' % (str(output),))
  return HttpResponseRedirect(request.META.get('HTTP_REFERER',
                              reverse('movie.vote.views.index')))

@login_required
def plus_vote(request, movie_name, output=None):
  movie = get_object_or_404(Movie, pk=movie_name)
  downvoters = movie.downvoters.filter(username=request.user.username)
  if downvoters:
    movie.downvoters.remove(request.user)
  else:
    movie.voters.add(request.user)
  movie.update_tally()
  movies = get_movies_for_ui(request.user.id, movie_name=movie_name)
  if output:
    movie_dict = movie_to_dict(get_movies_for_ui(request.user.id, movie_name=movie_name)[0])
    return HttpResponse(simplejson.dumps(movie_dict),
                        mimetype='application/%s' % (str(output),))
  return HttpResponseRedirect(request.META.get('HTTP_REFERER',
                              reverse('movie.vote.views.index')))

@login_required
def password_change(request):
  if request.method == 'POST':
    form = ChangePasswordForm(request.POST)
    if form.is_valid():
      request.user.set_password(form.cleaned_data['password1'])
      request.user.save()
      return HttpResponseRedirect(reverse('movie.vote.views.index'))
  else:
    form = ChangePasswordForm()
  return render_to_response('registration/change_password.html', {'form': form})

def new_user(request):
  if request.method == 'POST':
    form = NewUserForm(request.POST)
    if form.is_valid():
      user = User.objects.create_user(
        form.cleaned_data['username'],
        form.cleaned_data['email'],
        form.cleaned_data['password1'])
      user.save()
      user = authenticate(username=form.cleaned_data['username'],
                          password=form.cleaned_data['password1'])
      login(request, user)
      return HttpResponseRedirect(reverse('movie.vote.views.index'))
  else:
    form = NewUserForm()
  return render_to_response('registration/new_user.html', {'form': form})

def forgot_password(request):
  if request.method == 'POST':
    form = ForgotPasswordForm(request.POST)
    if form.is_valid():
      user = User.objects.filter(email=form.cleaned_data['email'])[0]
      id = random_string(40)
      user.message_set.create(message=id)
      url = request.build_absolute_uri('/accounts/password_reset/%s/%s/' %
                                       (user.username, id))
      t = loader.get_template('registration/password_reset_email.txt')
      c = { 'url': url, 'username': user.username }
      send_mail('Movie Vote password reset', t.render(Context(c)), None, [user.email])
      return render_to_response('registration/forgot_password_done.html', {'form': form})
  else:
    form = ForgotPasswordForm()
  return render_to_response('registration/forgot_password.html', {'form': form})

def password_reset(request, user, id):
  user = get_object_or_404(User, username=user)
  if id not in user.get_and_delete_messages():
    # No ID found
    return render_to_response('registration/forgot_password_fail.html')

  # Set a random password, login, then redirect to change password form
  random_passwd = User.objects.make_random_password()
  user.set_password(random_passwd)
  user.save()
  login(request, authenticate(username=user.username, password=random_passwd))
  return HttpResponseRedirect(reverse('movie.vote.views.password_change'))
